﻿using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.UGUI;
using KuiHuaBaoDian.Services.UGUI;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian.Services.UGUI {

    public interface IUIEventListener {

        void AddListener(Type eventType, Func<IUIEvent, UniTask> func);

        void RemoveListener(Type eventType, Func<IUIEvent, UniTask> func);
    }

    public interface IUIEventDispatcher {
    }

    internal sealed class KHBD_UIEventDispatcher : IUIEventListener, IUIEventDispatcher, IUIEventDispatcherInternal {

        public IUIEventListener AsListener() => this;

        private record Record(Type Type, Func<IUIEvent, UniTask> Func);

        private readonly IDictionary<Type, List<Func<IUIEvent, UniTask>>> m_Dictionary = new Dictionary<Type, List<Func<IUIEvent, UniTask>>>();
        private readonly List<Record> m_Removing = new();
        private readonly List<Record> m_Adding = new();
        private int m_DispatchingHash;

        public KHBD_UIEventDispatcher() {
            Daemon().Forget();
        }

        public void AddListener(Type eventType, Func<IUIEvent, UniTask> func) {
            Assert.IsNotNull(eventType);
            Assert.IsNotNull(func);

            var eventHash = eventType.GetHashCode();
            if (eventHash == m_DispatchingHash) {
                m_Adding.Add(new Record(eventType, func));
            } else {
                DoAdd(eventType, func);
            }
        }

        private void DoAdd(Type eventType, Func<IUIEvent, UniTask> func) {
            if (!m_Dictionary.TryGetValue(eventType, out var list)) {
                list = new List<Func<IUIEvent, UniTask>>();
                m_Dictionary.Add(eventType, list);
            }
            list.Add(func);
        }

        public void RemoveListener(Type eventType, Func<IUIEvent, UniTask> func) {
            Assert.IsNotNull(eventType);
            Assert.IsNotNull(func);

            var eventHash = eventType.GetHashCode();
            if (eventHash == m_DispatchingHash) {
                m_Removing.Add(new Record(eventType, func));
            } else {
                DoRemove(eventType, func);
            }
        }

        private void DoRemove(Type eventType, Func<IUIEvent, UniTask> func) {
            if (!m_Dictionary.TryGetValue(eventType, out var list)) {
                list = new List<Func<IUIEvent, UniTask>>();
                m_Dictionary.Add(eventType, list);
            }
            list.Remove(func);
        }

        public async UniTask Dispatch(IUIEvent @event) {
            Assert.IsNotNull(@event);
            Func<UniTask, UniTask> onTask = null;
            Func<IEnumerable<UniTask>, IEnumerable<UniTask>, UniTask> onTasks = null;
            if (@event is IUIEventTaskProcessing taskEvent) {
                onTask = taskEvent.OnTask;
                onTasks = taskEvent.OnTasks;
            }
            var eventType = @event.GetType();
            m_DispatchingHash = eventType.GetHashCode();
            if (m_Dictionary.TryGetValue(eventType, out var list)) {
                List<UniTask> taskBeforeList = null;
                List<UniTask> taskAfterList = null;
                if (onTasks != null) {
                    taskBeforeList = new List<UniTask>(list.Count);
                    taskAfterList = new List<UniTask>(list.Count);
                }
                foreach (var @object in list) {
                    var func = @object;
                    var task = func.Invoke(@event);
                    if (onTasks != null) {
                        taskBeforeList.Add(task);
                    }
                    if (onTask != null) {
                        task = onTask.Invoke(task);
                    }
                    if (onTasks != null) {
                        taskAfterList.Add(task);
                    }
                    await task;
                }
                if (onTasks != null) {
                    await onTasks.Invoke(taskBeforeList, taskAfterList);
                }
            }
            m_DispatchingHash = 0;
        }

        private async UniTaskVoid Daemon() {
            await KHBD.Context.WaitWhile(() => {
                var succeed = new HashSet<Record>();
                foreach (var adding in m_Adding) {
                    var eventHash = adding.Type.GetHashCode();
                    if (m_DispatchingHash != eventHash) {
                        DoAdd(adding.Type, adding.Func);
                        succeed.Add(adding);
                    }
                }
                foreach (var item in succeed) {
                    m_Adding.Remove(item);
                }
                succeed.Clear();
                foreach (var removing in m_Removing) {
                    var eventHash = removing.Type.GetHashCode();
                    if (m_DispatchingHash != eventHash) {
                        DoRemove(removing.Type, removing.Func);
                        succeed.Add(removing);
                    }
                }
                foreach (var item in succeed) {
                    m_Removing.Remove(item);
                }
                return true;
            }, CancellationToken.None);
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.UGUI {

    public interface IUIEventDispatcherInternal : IUIEventDispatcher {

        UniTask Dispatch(IUIEvent @event);

        IUIEventListener AsListener();
    }
}
